//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using UnityEngine;

namespace Flare.Geom
{
    /// <summary>
    /// A color transform allows the color of a display object to be adjusted. All four
    /// color channels (red, green, blue, and alpha) of a display object can be adjusted
    /// by a color transform. When applied to a display object the resulting color is
    /// calculated as follows:
    /// 
    ///   * red' = (red * redMultiplier) + redOffset
    ///   * green' = (green * greenMultiplier) + greenOffset
    ///   * blue' = (blue * blueMultiplier) + blueOffset
    ///   * alpha' = (alpha * alphaMultiplier) + alphaOffset
    /// 
    /// The resulting values are clamped to the range 0 to 255.
    /// </summary>
    public class ColorTransform
    {
        /// <summary>
        /// Access the alpha multiplier of the color transform.
        /// </summary>
        public float alphaMultiplier { get; set; }

        /// <summary>
        /// Access the alpha offset of the color transform.
        /// </summary>
        public float alphaOffset { get; set; }

        /// <summary>
        /// Access the blue multiplier of the color transform.
        /// </summary>
        public float blueMultiplier { get; set; }

        /// <summary>
        /// Access the blue offset of the color transform.
        /// </summary>
        public float blueOffset { get; set; }

        /// <summary>
        /// Access the green multiplier of the color transform.
        /// </summary>
        public float greenMultiplier { get; set; }

        /// <summary>
        /// Access the green offset of the color transform.
        /// </summary>
        public float greenOffset { get; set; }

        /// <summary>
        /// Access the red multiplier of the color transform.
        /// </summary>
        public float redMultiplier { get; set; }

        /// <summary>
        /// Access the red offset of the color transform.
        /// </summary>
        public float redOffset { get; set; }

        /// <summary>
        /// Create a new color transform using the given values. The default values will
        /// set the color transform to the identity transformation.
        /// </summary>
        public ColorTransform(float redMultiplier = 1.0f, float greenMultiplier = 1.0f,
            float blueMultiplier = 1.0f, float alphaMultiplier = 1.0f,
            float redOffset = 0.0f, float greenOffset = 0.0f, float blueOffset = 0.0f,
            float alphaOffset = 0.0f)
        {
            this.redMultiplier = redMultiplier;
            this.greenMultiplier = greenMultiplier;
            this.blueMultiplier = blueMultiplier;
            this.alphaMultiplier = alphaMultiplier;

            this.redOffset = redOffset;
            this.greenOffset = greenOffset;
            this.blueOffset = blueOffset;
            this.alphaOffset = alphaOffset;
        }

        /// <summary>
        /// Create a new color transform by copying the values of the given color transform.
        /// </summary>
        /// <param name="c">The given color transform to copy.</param>
        public ColorTransform(ColorTransform c)
        {
            this.redMultiplier = c.redMultiplier;
            this.greenMultiplier = c.greenMultiplier;
            this.blueMultiplier = c.blueMultiplier;
            this.alphaMultiplier = c.alphaMultiplier;

            this.redOffset = c.redOffset;
            this.greenOffset = c.greenOffset;
            this.blueOffset = c.blueOffset;
            this.alphaOffset = c.alphaOffset;
        }

        /// <summary>
        /// Concatenates the given color transform with this color transform, combining their
        /// effects.
        /// </summary>
        /// <param name="c">The given color transform to concatenate.</param>
        public void Concat(ColorTransform c)
        {
            this.redOffset = this.redOffset + c.redOffset * this.redMultiplier;
            this.redMultiplier = this.redMultiplier * c.redMultiplier;

            this.greenOffset = this.greenOffset + c.greenOffset * this.greenMultiplier;
            this.greenMultiplier = this.greenMultiplier * c.greenMultiplier;

            this.blueOffset = this.blueOffset + c.blueOffset * this.blueMultiplier;
            this.blueMultiplier = this.blueMultiplier * c.blueMultiplier;

            this.alphaOffset = this.alphaOffset + c.alphaOffset * this.alphaMultiplier;
            this.alphaMultiplier = this.alphaMultiplier * c.alphaMultiplier;

            if (this.redOffset > 255.0f)
                this.redOffset = 255.0f;
            else if (this.redOffset < -255.0f)
                this.redOffset = -255.0f;

            if (this.greenOffset > 255.0f)
                this.greenOffset = 255.0f;
            else if (this.greenOffset < -255.0f)
                this.greenOffset = -255.0f;

            if (this.blueOffset > 255.0f)
                this.blueOffset = 255.0f;
            else if (this.blueOffset < -255.0f)
                this.blueOffset = -255.0f;

            if (this.alphaOffset > 255.0f)
                this.alphaOffset = 255.0f;
            else if (this.alphaOffset < -255.0f)
                this.alphaOffset = -255.0f;
        }

        /// <summary>
        /// Copy the values of the given color transform.
        /// </summary>
        /// <param name="c">The given color transform to copy.</param>
        public void CopyFrom(ColorTransform c)
        {
            this.redMultiplier = c.redMultiplier;
            this.greenMultiplier = c.greenMultiplier;
            this.blueMultiplier = c.blueMultiplier;
            this.alphaMultiplier = c.alphaMultiplier;
            
            this.redOffset = c.redOffset;
            this.greenOffset = c.greenOffset;
            this.blueOffset = c.blueOffset;
            this.alphaOffset = c.alphaOffset;
        }

        /// <summary>
        /// Sets the color transform to the identity transformation. 
        /// </summary>
        public void Identity()
        {
            this.redMultiplier = 1.0f;
            this.greenMultiplier = 1.0f;
            this.blueMultiplier = 1.0f;
            this.alphaMultiplier = 1.0f;
            
            this.redOffset = 0.0f;
            this.greenOffset = 0.0f;
            this.blueOffset = 0.0f;
            this.alphaOffset = 0.0f;
        }

        /// <summary>
        /// Returns a formatted string representation of the color transform.
        /// </summary>
        /// <returns>The color transform represented as a string.</returns>
        public override string ToString()
        {
            return string.Format("(redMultiplier={0}, greenMultiplier={1}, blueMultiplier={2}, "
                + "alphaMultiplier={3}, redOffset={4}, greenOffset={5}, blueOffset={6}, "
                + "alphaOffset={7})",
                redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier,
                redOffset, greenOffset, blueOffset, alphaOffset);
        }

        internal UnityEngine.Color ApplyToColor(uint color, float alpha = 1.0f)
        {
            float r = (((color >> 16) & 0xff) * this.redMultiplier) + this.redOffset;
            float g = (((color >> 8) & 0xff) * this.greenMultiplier) + this.greenOffset;
            float b = (((color) & 0xff) * this.blueMultiplier) + this.blueOffset;
            float a = ((255.0f * alpha) * this.alphaMultiplier) + this.alphaOffset;

            // Clamp and premultiply alpha
            float cpa = Mathf.Clamp01(a / 255.0f);

            Color result = new Color(Mathf.Clamp01(r / 255.0f) * cpa,
                Mathf.Clamp01(g / 255.0f) * cpa,
                Mathf.Clamp01(b / 255.0f) * cpa,
                cpa);

            return result;
        }

        internal UnityEngine.Color ApplyToARGB(uint color)
        {
            float r = (((color >> 16) & 0xff) * this.redMultiplier) + this.redOffset;
            float g = (((color >> 8) & 0xff) * this.greenMultiplier) + this.greenOffset;
            float b = (((color) & 0xff) * this.blueMultiplier) + this.blueOffset;
            float a = (((color >> 24) & 0xff) * this.alphaMultiplier) + this.alphaOffset;

            // Clamp and premultiply alpha
            float cpa = Mathf.Clamp01(a / 255.0f);

            Color result = new Color(Mathf.Clamp01(r / 255.0f) * cpa,
                Mathf.Clamp01(g / 255.0f) * cpa,
                Mathf.Clamp01(b / 255.0f) * cpa,
                cpa);

            return result;
        }
    }
}